/* * Copyright 2013 Adobe Systems Incorporated * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.adobe.people.jedelson.cq.urlfilter.impl; import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.regex.Pattern; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import org.apache.commons.lang.ArrayUtils; import org.apache.felix.scr.annotations.sling.SlingFilter; import org.apache.felix.scr.annotations.sling.SlingFilterScope; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; import org.apache.sling.api.request.RequestPathInfo; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ValueMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.day.cq.wcm.api.components.Component; import com.day.cq.wcm.api.components.ComponentManager; /** * Illustration of a technique for url filtering on suffixes, selectors, and/or * extensions */ @SlingFilter(scope = SlingFilterScope.REQUEST, order = Integer.MIN_VALUE) public class UrlFilter implements Filter { static final String PN_ALLOWED_EXTENSION_PATTERN = "allowedExtensionPattern"; static final String PN_ALLOWED_EXTENSIONS = "allowedExtensions"; static final String PN_ALLOWED_SELECTOR_PATTERN = "allowedSelectorPattern"; static final String PN_ALLOWED_SELECTORS = "allowedSelectors"; static final String PN_ALLOWED_SUFFIX_PATTERN = "allowedSuffixPattern"; static final String PN_ALLOWED_SUFFIXES = "allowedSuffixes"; static final Collection<String> PROPERTY_NAMES = Arrays.asList(PN_ALLOWED_SUFFIXES, PN_ALLOWED_EXTENSIONS, PN_ALLOWED_SELECTORS, PN_ALLOWED_SUFFIX_PATTERN, PN_ALLOWED_SELECTOR_PATTERN, PN_ALLOWED_EXTENSION_PATTERN); private Logger logger = LoggerFactory.getLogger(this.getClass()); public void destroy() { // nothing to do } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (request instanceof SlingHttpServletRequest && response instanceof SlingHttpServletResponse) { SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request; SlingHttpServletResponse slingResponse = (SlingHttpServletResponse) response; RequestPathInfo pathInfo = slingRequest.getRequestPathInfo(); Component definitionComponent = findUrlFilterDefinitionComponent(slingRequest.getResource(), slingRequest.getResourceResolver().adaptTo(ComponentManager.class)); if (definitionComponent != null) { String definitionPath = definitionComponent.getPath(); logger.debug("found url filter definition resource at {}", definitionPath); ValueMap properties = definitionComponent.getProperties(); if (properties != null) { if (checkSelector(pathInfo, properties) && checkSuffix(pathInfo, properties) && checkExtension(pathInfo, properties)) { logger.debug("url filter definition resource at {} passed for request {}.", definitionPath, slingRequest.getRequestPathInfo()); } else { logger.info("url filter definition resource at {} FAILED for request {}.", definitionPath, slingRequest.getRequestPathInfo()); slingResponse.sendError(403); return; } } } } chain.doFilter(request, response); } public void init(FilterConfig filterConfig) throws ServletException { // nothing to do } boolean checkExtension(RequestPathInfo pathInfo, ValueMap properties) { return check(pathInfo.getExtension(), PN_ALLOWED_EXTENSIONS, PN_ALLOWED_EXTENSION_PATTERN, properties); } boolean checkSelector(RequestPathInfo pathInfo, ValueMap properties) { return check(pathInfo.getSelectorString(), PN_ALLOWED_SELECTORS, PN_ALLOWED_SELECTOR_PATTERN, properties); } boolean check(String value, String allowedArrayPropertyName, String allowedPatternPropertyName, ValueMap properties) { if (value == null) { // no value is always allowed return true; } String[] allowedValues = properties.get(allowedArrayPropertyName, String[].class); if (allowedValues != null) { if (allowedValues.length == 0) { logger.debug("{} was empty, therefore not allowing any value.", allowedArrayPropertyName); return false; } else if (!ArrayUtils.contains(allowedValues, value)) { logger.debug("{} did not contain our string {}. checking the pattern.", allowedArrayPropertyName, value); String allowedPattern = properties.get(allowedPatternPropertyName, String.class); if (allowedPattern == null || !Pattern.matches(allowedPattern, value)) { logger.debug("allowedPattern ({}) did not match our string {}", allowedPattern, value); return false; } else { logger.debug("allowedPattern ({}) did match our string {}", allowedPattern, value); return true; } } else { return true; } } else { String allowedPattern = properties.get(allowedPatternPropertyName, String.class); if (allowedPattern != null && !Pattern.matches(allowedPattern, value)) { logger.debug("allowedPattern ({}) did not match our string {}", allowedPattern, value); return false; } else { return true; } } } boolean checkSuffix(RequestPathInfo pathInfo, ValueMap properties) { return check(pathInfo.getSuffix(), PN_ALLOWED_SUFFIXES, PN_ALLOWED_SUFFIX_PATTERN, properties); } Component findUrlFilterDefinitionComponent(Resource resource, ComponentManager componentManager) { if (resource == null) { return null; } Resource contentResource = resource.getChild("jcr:content"); if (contentResource != null) { resource = contentResource; } Component component = componentManager.getComponentOfResource(resource); return findUrlFilterDefinitionComponent(component); } private Component findUrlFilterDefinitionComponent(Component component) { if (component == null) { return null; } ValueMap properties = component.getProperties(); // Collections.disjoint returns true if the collections // have nothing in common, so when it is false, use the current resource if (!Collections.disjoint(properties.keySet(), PROPERTY_NAMES)) { return component; } else { // otherwise, look at the resource type resource's super type return findUrlFilterDefinitionComponent(component.getSuperComponent()); } } }